package be.kuleuven.cs.mop.domain.model.impl;


import java.util.Calendar;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import be.kuleuven.cs.mop.domain.exceptions.TaskManagerException;
import be.kuleuven.cs.mop.domain.model.Reservation;
import be.kuleuven.cs.mop.domain.model.Resource;


/**
 * Domain class implementation of a resource
 * Implements the {@link Resource} interface
 */
public class ResourceImpl extends WorldElement
		implements Comparable<ResourceImpl>, Resource {
	
	/**
	 * A resource has a type, a name and a set of reservations
	 */
	private final ResourceTypeImpl 		resourceType;
	private final String				description;
	private final Set<ReservationImpl>	reservations = new HashSet<ReservationImpl>();		
	
	/**
	 * Creates a new {@link ResourceImpl}
	 * The created resource is automatically added to the system
	 * 
	 * @param 	world 			- The {@link World}
	 * @param 	description 	- The name for this new {@link ResourceImpl} 
	 * @param	resourceType 	- The {@link ResourceTypeImpl} for this new {@link ResourceImpl}
	 * 
	 * @throws	TaskManagerException RESOURCE_INVALID_DESCRIPTION 	- !{@link isValidDescription(String)}
	 * @throws 	TaskManagerExceptionor INVALID_RESOURCE_TYPE		- !{@link isValidResourceType(ResourceTypeImpl)}
	 */
	public ResourceImpl(final World world, final String description, ResourceTypeImpl resourceType)
			throws TaskManagerException {
		super(world);
		
		if(!isValidDescription(description))
			throw new TaskManagerException(TaskManagerException.RESOURCE_INVALID_DESCRIPTION);
		if(!isValidResourceType(resourceType))
			throw new TaskManagerException(TaskManagerException.INVALID_RESOURCE_TYPE);
		
		this.description = description;
		this.resourceType = resourceType;
		
		getWorld().add(this);
	}

	
	
	/**
	 * Checks if the given Resource Type is not null and present in the World
	 * @param resourceType - The given {@link ResourceTypeImpl}
	 * @return false if resourceType is null or not present in the system
	 */
	private boolean isValidResourceType(ResourceTypeImpl resourceType) {
		return resourceType != null && getWorld().getResourceTypes().contains(resourceType);
	}

	/**
	 * Creates a {@link ReservationImpl} and adds it to this {@link ResourceImpl}
	 * 
	 * @return the created {@link ReservationImpl}
	 * @throws TaskManagerException RESERVATION_CONFLICT - The new reservation is in conflict with other reservations for this resource
	 */
	protected ReservationImpl addReservation(final TaskImpl task, final Calendar date,
			final long duration) throws TaskManagerException {
		final ReservationImpl reservation = new ReservationImpl(task, this, date, duration);
		for (final ReservationImpl r : reservations)
			if (reservation.hasConflict(r))
				throw new TaskManagerException(TaskManagerException.RESOURCE_RESERVATION_CONFLICT);
		reservations.add(reservation);
		return reservation;
	}
	
	@Override
	public int compareTo(final ResourceImpl resource) {
		return -new Integer(getReservations().size()).compareTo(
				resource.getReservations().size());
	}
	
	@Override
	public String getDescription() { return description; }
	
	@Override
	public Set<ReservationImpl> getReservations() {
		return Collections.unmodifiableSet(reservations);
	}
	

	/**
	 * Removes the specified {@link Reservation} from this {@link Resource}
	 * @param reservation - a <code>MyReservation</code>
	 * @note Will only be called upon task removal to keep the system in a consistent state
	 */
	
	void removeReservation(final ReservationImpl reservation) throws TaskManagerException {
		if (reservation == null)
			throw new NullPointerException("Reservation == NULL");
		
		if (!reservations.remove(reservation))
			throw new TaskManagerException(TaskManagerException.RESERVATION_DOES_NOT_EXIST);
		
	}
	
	@Override
	public String toString() {
		return String.format("[%s]  %s", getResourceType(), getDescription());
	}
	
	/**
	 * Validates the given description
	 * @param descr	- The given description
	 * @return false if descr is null, empty or already in use by some other resource in the system
	 */
	private boolean isValidDescription(final String descr) {
		return ((descr != null) && !descr.isEmpty() && !resourceAlreadyExists(descr));
	}
	
	/**
	 * Checks whether a given resource name is already in use by an earlier created resource
	 * @param name - name of resource
	 * @return false if name is already in use by an earlier created resource
	 */
	private boolean resourceAlreadyExists(String name) {
		for(ResourceImpl resource : getWorld().getResources()) {
			if(resource.getDescription().equals(name))
				return true;
		}
		return false;
	}

	/**
	 * Returns the type of this resource
	 */
	public ResourceTypeImpl getResourceType() {
		return resourceType;
	}
	
}